const Ret = require("./ret.js");
const Param = require("./param.js");
const qs = require("qs");
const getBody = require("body");
const multiparty = require("multiparty");
const { promisify } = require("util");
const zlib = require("zlib");

module.exports = class Router {
  static #defaultFormat = true;

  /**
   * 根据request路由
   * @param {object} targets 路由表。支持before和after，如果提供了会在执行主方法前后执行。需要async的方法。并且before返回值为Ret，如果不为true，会直接返回
   * @param {*} event 事件载体
   * @param {*} context 系统属性
   * @returns 设置了useDefaultFormat为true，会返回错误信息
   */
  static async routeEvent(targets, event, context) {
    const eventObj = JSON.parse(event.toString());
    const req = {
      path: eventObj.rawPath,
      method: eventObj.requestContext.http.method,
      sourceIp: eventObj.requestContext.http.sourceIp,
      queries: eventObj.queryParameters,
      headers: eventObj.headers,
      body: eventObj.isBase64Encoded
        ? Buffer.from(eventObj.body, "base64").toString("utf-8")
        : eventObj.body,
      isBase64Encoded: eventObj.isBase64Encoded,
    };
    // 格式兼容
    if (req.headers) {
      req.headers["content-type"] = req.headers["Content-Type"];
    }
    let res = {
      content: "",
      headers: {},
      send: function (content) {
        this.content = content;
      },
      setHeader: function (key, value) {
        this.headers[key] = value;
      },
    };
    // 转换后调用原来的方法
    await this.route(targets, req, res, context);
    // 看看返回的headers中有没有content-type，没有的话默认为json
    if (!res.headers["content-type"] && !res.headers["Content-Type"]) {
      res.headers["content-type"] = "application/json";
    }
    return {
      statusCode: res.statusCode || 200,
      headers: res.headers,
      body: res.content.toString("base64"),
      isBase64Encoded: true,
    };
  }

  /**
   * 根据request路由
   * @param {object} targets 路由表。支持before和after，如果提供了会在执行主方法前后执行。需要async的方法。并且before返回值为Ret，如果不为true，会直接返回
   * @param {*} req 请求
   * @param {*} res 响应
   * @param {*} context 系统属性
   * @returns 设置了useDefaultFormat为true，会返回错误信息
   */
  static async route(targets, req, res, context) {
    const startTime = new Date().getTime();
    const { methodName, className } = this._getPath(req);
    // 兼容非阿里云
    await this._handleParams(req);
    console.log(
      "p:",
      req.method,
      req.path,
      req.headers
        ? req.headers["content-type"] || req.headers["Content-Type"]
        : "",
      "q:",
      req.queries ? qs.stringify(req.queries).substring(0, 500) : "",
      "b:",
      req.sourceBody ? req.sourceBody.substring(0, 500) : "",
      req.files ? "f:" : "",
      req.files ? req.params : "",
      req.files ? req.files : ""
    );

    if (methodName) {
      const controller = targets[className];
      if (controller == undefined) {
        return this._sendError("请输入正确路径", res);
      }
      const method = controller[methodName];
      if (method) {
        try {
          // before
          if (targets.before) {
            console.log("start before action");
            const beforeResult = await targets.before(req, res, context);
            if (beforeResult && !beforeResult.success) {
              this._handleReturn(beforeResult, startTime, req, res);
              return;
            }
          }
          // 需要直接调用，不能用中间变量
          let result = await controller[methodName](req, res, context);
          if (result == undefined) {
            result = new Ret(true);
          }
          // after
          if (targets.after) {
            console.log("start after action");
            await targets.after(req, res, context);
          }
          // controller方法有返回值的自动send
          this._handleReturn(result, startTime, req, res);
        } catch (e) {
          if (e instanceof Error) {
            return this._sendError(e.message ? e.message : "出错啦", res, true);
          } else if (e instanceof Ret) {
            return this._handleReturn(e, startTime, req, res);
          } else {
            return this._sendError(e, res, false);
          }
        }
      } else {
        return this._sendError("请输入正确方法", res);
      }
    } else {
      return this._sendError("请输入方法", res);
    }
  }

  static _getPath(req) {
    const paths = req.path.split("/");
    const className = paths.length > 2 ? "/" + paths[paths.length - 2] : "/";
    const methodName = paths[paths.length - 1];
    return { methodName, className };
  }

  static async _handleParams(req) {
    if (req.query) {
      req.queries = req.query;
    }
    const isFile =
      req.headers &&
      req.headers["content-type"] &&
      req.headers["content-type"].includes("multipart/form-data");
    const isJson =
      req.headers &&
      req.headers["content-type"] &&
      req.headers["content-type"].includes("application/json");
    if (req.method == "POST") {
      if (isFile) {
        await this._getFile(req);
      } else if (isJson) {
        // 3.0是有body的，2.0是没有body的
        const data =
          req.body == undefined ? await promisify(getBody)(req) : req.body;
        if (!Param.isBlank(data)) {
          try {
            req.sourceBody = data.toString();
            req.body = JSON.parse(data.toString());
          } catch (e) {
            console.warn("body格式错误", data);
          }
        }
      } else {
        const data =
          req.body == undefined ? await promisify(getBody)(req) : req.body;
        try {
          req.sourceBody = decodeURIComponent(data.toString());
        } catch (e) {
          req.sourceBody = data.toString();
        }
        req.body = qs.parse(data.toString());
      }
    }
    if (req.params == undefined) {
      req.params = {};
    } else {
      // express特有的 0为路径，处理一下
      delete req.params[0];
    }

    if (!Param.isBlank(req.queries)) {
      Object.keys(req.queries).forEach((n) => (req.params[n] = req.queries[n]));
    }
    if (!Param.isBlank(req.body) && !isFile) {
      Object.keys(req.body).forEach((n) => (req.params[n] = req.body[n]));
    }
  }

  static _getFile(req) {
    return new Promise((resolve, reject) => {
      const form = new multiparty.Form();

      form.parse(req, function (err, fields, files) {
        if (err) reject(err);
        req.params = fields;
        req.files = files;
        resolve();
      });
    });
  }

  static _handleReturn(result, startTime, req, res) {
    const useTime = (new Date().getTime() - startTime) / 1000;
    if (Buffer.isBuffer(result)) {
      res.send(result);
    } else {
      if (!Param.isBlank(result)) {
        let content;
        if (result instanceof Ret) {
          content = result.toString();
        } else {
          if (typeof result === "string") {
            content = result;
          } else {
            content = JSON.stringify(result);
          }
        }
        console.log(
          useTime + "s",
          req.path,
          "response:",
          content.substring(0, 500)
        );
        // 处理header带压缩支持的情况
        if (req.headers) {
          const acceptEncoding =
            req.headers["Accept-Encoding"] || req.headers["accept-encoding"];
          if (acceptEncoding) {
            if (acceptEncoding.includes("br")) {
              content = zlib.brotliCompressSync(content);
              res.setHeader("Content-Encoding", "br");
            } else if (acceptEncoding.includes("gzip")) {
              content = zlib.gzipSync(content);
              res.setHeader("Content-Encoding", "gzip");
            } else if (acceptEncoding.includes("deflate")) {
              content = zlib.deflateSync(content);
              res.setHeader("Content-Encoding", "deflate");
            }
          }
        }
        res.send(content);
      }
    }
  }

  static _sendError(message, res, errorLevel = false) {
    if (errorLevel) {
      console.error(message);
    } else {
      console.warn(message);
    }
    if (this.#defaultFormat) {
      res.send(
        new Ret(
          false,
          message,
          /请输入|参数/.test(message) ? Ret.CODE_PARAMETER : Ret.CODE_SYSTEM
        ).toString()
      );
    }
    return message;
  }

  /**
   * 是否使用默认格式返回错误的情况
   * @param {boolean} isUse
   */
  static useDefaultFormat(isUse) {
    this.#defaultFormat = isUse;
  }
};
